package com.yhy.common.intercept;

import com.google.common.collect.Sets;
import com.yhy.common.constants.BusPrivCode;
import com.yhy.common.context.ThreadContextHelper;
import com.yhy.common.dto.AppReturnMsg;
import com.yhy.common.dto.BaseDTO;
import com.yhy.common.dto.ReturnCode;
import com.yhy.common.exception.BusinessException;
import com.yhy.common.token.JwtHelper;
import com.yhy.common.token.TokenState;
import com.yhy.common.utils.*;
import com.yhy.common.vo.SysUser;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

/**
 * 拦截器：记录用户操作日志，检查用户权限
 *
 * @author yanghuiyuan
 */

@Aspect
@Component
public class ActionInterceptor {

    private static final Logger LOGGER = LoggerFactory.getLogger(ActionInterceptor.class);

    @Value("${spring.profiles.active}")
    private String env;

    /**
     * 定义拦截规则：拦截com.yhy.*.action包下面的所有类中，有@RequestMapping 注解的方法。
     */
    @Pointcut("((execution(* com.yhy.*.action..*(..)))) && (@annotation(org.springframework.web.bind.annotation.RequestMapping) || @annotation(org.springframework.web.bind.annotation.GetMapping)" +
            "|| @annotation(org.springframework.web.bind.annotation.PostMapping))")
    //@Pointcut("((execution(* com.yhy.*.action..*(..))) || (execution(* com.yhy.common.action.BaseBillAction.*(..))) " +
    //        " || (execution(* com.yhy.common.action.BaseAction.*(..)))) && @annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public void actionMethodPointcut() {
    }

    /**
     * 拦截器具体实现
     *
     * @param pjp
     * @return JsonResult（被拦截方法的执行结果，或需要登录的错误提示。）
     */
    @Around("actionMethodPointcut()") //指定拦截器规则
    public Object Interceptor(ProceedingJoinPoint pjp) {
        long beginTime = System.currentTimeMillis();
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod(); //获取被拦截的方法
        String methodName = method.getName(); //获取被拦截的方法名
        Class<?> returnType = method.getReturnType();

        Set<Object> allParams = new LinkedHashSet<>(); //保存所有请求参数，用于输出到日志中

        Object result = null;
        String requestIp = "";

        Object[] args = pjp.getArgs();
        for (Object arg : args) {
            if (arg instanceof BaseDTO) {
                BaseDTO baseDTO = (BaseDTO) arg;
                allParams.add(baseDTO);
            } else if (arg instanceof Map<?, ?>) {
                //提取方法中的MAP参数，用于记录进日志中
                @SuppressWarnings("unchecked")
                Map<String, Object> map = (Map<String, Object>) arg;

                allParams.add(map);
            } else if (arg instanceof HttpServletRequest) {
                HttpServletRequest request = (HttpServletRequest) arg;
                requestIp = WebUtils.getIpAddr(request);
                if (isLoginRequired(method)) {
                    if (!isLogin(request)) {
                        result = new AppReturnMsg(ReturnCode.LOGIN_FAIL.getCode(), "登录失败");
                    }
                }
                //获取query string 或 posted form data参数
                Map<String, String[]> paramMap = request.getParameterMap();
                if (paramMap != null && paramMap.size() > 0) {
                    allParams.add(paramMap);
                }
            } else if (arg instanceof HttpServletResponse) {
                //do nothing...
            } else if (arg instanceof MultipartFile){
                //do nothing...
            } else {
                allParams.add(arg);
            }
        }
        //权限校验
        Set<String> privCodes = isPrivChecked(method);
        if(privCodes != null) {
            SysUser sysUser = YhyUtils.getSysUser();
            if(sysUser == null) {
                result = new AppReturnMsg(ReturnCode.PRIV_FAIL.getCode(), "请刷新页面.无权限访问");
            } else {
                Object cacheCodes = RedisUtil.get(ConstantUtil.PRIV_CODE_PREFIX + sysUser.getSourceType() + sysUser.getUserAccount());
                if(cacheCodes == null) {
                    result = new AppReturnMsg(ReturnCode.PRIV_FAIL.getCode(), "无权限");
                } else {
                    Set<String> hasPrivCodes = (Set<String>) cacheCodes;
                    boolean hasPriv = false;
                    if(hasPrivCodes.contains(BusPrivCode.PRIV_SYS_ADMIN.getVal())) {
                        hasPriv = true;
                    }
                    if(!hasPriv) {
                        for (String privCode : privCodes) {
                            if(hasPrivCodes.contains(privCode)) {
                                hasPriv = true;
                                break;
                            }
                        }
                        if(!hasPriv) {
                            result = new AppReturnMsg(ReturnCode.PRIV_FAIL.getCode(), "无权限访问");
                        }
                    }
                }
            }
        }


        LOGGER.info("请求开始，IP: {}, 方法：{},参数:{}", requestIp, methodName, JsonUtils.toJson(allParams));

        try {
            if (result == null) {
                // 一切正常的情况下，继续执行被拦截的方法
                result = pjp.proceed();
            }
        } catch(BusinessException e) {
            LOGGER.error("BusinessException: ", e);
            result = new AppReturnMsg(ReturnCode.FAIL_CODE.getCode(), e.getMessage());
        } catch(Throwable e) {
            LOGGER.error("exception: ", e);
            result = new AppReturnMsg(ReturnCode.FAIL_CODE.getCode(), "发生异常：" + e.getMessage());
        } finally {
            ThreadContextHelper.releaseContext();
        }

        long costMs = System.currentTimeMillis() - beginTime;
        LOGGER.info("{}请求结束，耗时：{}ms", methodName, costMs);

        return result;
    }

    /**
     * 判断一个方法是否需要登录
     *
     * @param method
     * @return
     */
    private boolean isLoginRequired(Method method) {
        /*if (env.equals("dev")) {
            //只有生产环境才需要登录
            return false;
        }*/

        boolean result = true;
        if (method.isAnnotationPresent(LoginPermission.class)) {
            result = method.getAnnotation(LoginPermission.class).loginRequired();
        }
        return result;
    }

    private Set<String> isPrivChecked(Method method) {
        Set<String> roles = null;
        if (method.isAnnotationPresent(PreCheckPermission.class)) {
            roles = Sets.newHashSet(method.getAnnotation(PreCheckPermission.class).roles());
        }
        return roles;
    }

    //判断是否已经登录
    private boolean isLogin(HttpServletRequest request) {
        String token = request.getHeader(JwtHelper.AUTHORIZATION);
        Map<String, Object> resultMap = JwtHelper.validToken(token);
        TokenState state = TokenState.getInstByVal((String) resultMap.get("state"));
        switch (state) {
            case VALID:
                //取出payload中数据,放入到request作用域中
                Map<String,Object> dataMap = (Map<String, Object>) resultMap.get("data");
                String operatorCode = String.valueOf(dataMap.get(JwtHelper.CURRENT_USER));
                String sourceType = String.valueOf(dataMap.get(JwtHelper.SOURCE_TYPE));
                SysUser sysUser = (SysUser) RedisUtil.get(ConstantUtil.OPERATOR_PREFIX + sourceType + operatorCode);
                // SysUser sysUser = JsonUtils.fromJson(dataMap.get(JwtHelper.SYS_USER).toString(),SysUser.class);
                ThreadContextHelper.setParameter(JwtHelper.SYS_USER,sysUser);
                //request.setAttribute(JwtHelper.SYS_USER, sysUser);
                //request.setAttribute(JwtHelper.CURRENT_OPERATOR_ID,dataMap.get(JwtHelper.CURRENT_OPERATOR_ID));
                return true;
            case EXPIRED:
                LOGGER.error("token过期=======" + token);
                //resp.sendError(HttpServletResponse.SC_FORBIDDEN, url);
                return false;
            case INVALID:
                LOGGER.error("无效token" + token);
                //token过期或者无效，则输出错误信息返回给ajax
                /*JSONObject outputMSg=new JSONObject();
				outputMSg.put("success", false);
				outputMSg.put("msg", "您的token不合法或者过期了，请重新登陆");*/
                return false;
        }
        return false;
    }

}
